package spring.transactional;

import aop.Events;
import aop.advisor.AnnotationPointcut;
import aop.event.BeforeEvent;
import aop.event.ReturnEvent;
import aop.event.ThrowableEvent;
import aop.impl.EventsImpl;
import aop.listener.AbstractMethodListener;
import aop.listener.MethodListener;
import aop.vo.MethodVo;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttribute;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class TxMethodListener extends AbstractMethodListener implements InitializingBean {

    public static String packages;

    private final AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(false);
    private static final ConcurrentMap<String, TransactionAttribute> transactionAttributeMap = new ConcurrentHashMap<>();

    private final PlatformTransactionManager transactionManager;

    public TxMethodListener(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        add(packages, this);
    }

    @Override
    public void before(BeforeEvent event) {
//        System.out.println("aop: before."+event.getMethodSignature());
        TransactionAttribute definition = getTransactionAttribute(event.getThis().getClass(), event.getMethodSignature());
        TransactionStatus status = transactionManager.getTransaction(definition);
        event.setData(status);
        System.out.println("aop: start." + event.getMethodSignature() + " " + status.toString());
    }

    @Override
    public void afterReturn(ReturnEvent event) {
        TransactionStatus status = (TransactionStatus) event.getData();
        transactionManager.commit(status);
        System.out.println("aop: commit." + event.getMethodSignature() + " " + status.toString());
    }

    @Override
    public void exception(ThrowableEvent event) {
        TransactionStatus status = (TransactionStatus) event.getData();
        transactionManager.rollback(status);
        System.out.println("aop: rollback." + event.getMethodSignature() + " " + status.toString());
    }

    private TransactionAttribute getTransactionAttribute(Class clz, String signature) {
        TransactionAttribute transactionAttribute = transactionAttributeMap.get(signature);
        if (transactionAttribute != null) {
            return transactionAttribute;
        }
        // todo 做缓存
        MethodVo methodVo = MethodVo.of(signature);
        Method method = getMethod(clz, methodVo.getClassName(), methodVo.getMethodName(), methodVo.getParamTypes());
        transactionAttribute = transactionAttributeSource.getTransactionAttribute(method, clz);
        transactionAttributeMap.put(signature, transactionAttribute);
        return transactionAttribute;
    }

    private static Method getMethod(Class<?> clz, String className, String methodName, String[] paramTypes) {

        Method[] methods = clz.getDeclaredMethods();
        Optional<Method> first = Arrays.stream(methods).filter(m -> m.getName().equals(methodName)).filter(m ->
                Arrays.equals(MethodVo.getParamTypes(m.getParameterTypes())
                        , paramTypes)
        ).findFirst();
        return first.orElse(null);
    }

    public static void add(String packages, MethodListener listener) {
        Events events = EventsImpl.getInstance();

        events.addEventListener(new AnnotationPointcut(packages, org.springframework.transaction.annotation.Transactional.class.getName())
                , listener);
//        System.out.println("aop: add TxMethodListener");
//        events.addEventListener(new AnnotationPointcut(packages, Transactional.class.getName())
//                , listener);
    }
}
